﻿using BepInEx.Logging;
using CheatMenu.Menus;
using CheatMenu.Utils;
using Fungus;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.SceneManagement;
using Logger = BepInEx.Logger;

namespace CheatMenu.Managers
{
    public static class StateManager
    {
        public static bool HasPopulatedFlowcharts { get; private set; } = false;
        public static int InitCounter { get; private set; } = 0;
        public static int FlowchartCount { get; private set; } = 0;
        public static Scene LastScene { get; private set; }
        public static string LastSceneName { get => LastScene.name; }
        public static bool GUIEnabled { get; private set; } = true;

        static List<Flowchart> flowcharts;
        static List<FilteredFlowchart> filteredFlowcharts;
        static string filteredText;
        static readonly VariableComparer variableComparer = new VariableComparer();
        static int totalScenesToDump = 0;
        static bool dumpingScenes;
        static Queue<string> scenesToDump;
        static List<string> blackListScenes = new List<string>(new string[] { "Start" });

        private static Lazy<MenuStateViewer> StateViewerMenu = new Lazy<MenuStateViewer>(()=>MenuManager.GetMenu<MenuStateViewer>());
        private static Lazy<MenuRandomIntegerPicker> RandomIntegerPickerMenu = new Lazy<MenuRandomIntegerPicker>(() => MenuManager.GetMenu<MenuRandomIntegerPicker>());

        public static void Initialize()
        {
            SceneManager.activeSceneChanged += (oldScene, newScene) =>
            {
                HasPopulatedFlowcharts = false;
                flowcharts = null;
                filteredFlowcharts = null;
                InitCounter = 0;
                FlowchartCount = 0;
                LastScene = newScene;
                StateViewerMenu.Value.ResetScrollVector();
                RandomIntegerPickerMenu.Value.Shown = false;
                GUIEnabled = !blackListScenes.Contains(newScene.name);
            };
            LastScene = SceneManager.GetActiveScene();
            GUIEnabled = !blackListScenes.Contains(LastScene.name);
        }

        public static void OnUpdate()
        {
            if (!HasPopulatedFlowcharts)
            {
                if(InitCounter < 10)
                    InitFlowcharts();
            }
            else if (dumpingScenes)
            {
                SceneUtil.DumpScene();
                Logger.Log(LogLevel.Info, $"Dumping scene: {LastSceneName} ({totalScenesToDump - scenesToDump.Count}/{totalScenesToDump})");

                if (scenesToDump.Count > 0)
                {
                    string nextScene = scenesToDump.Dequeue();
                    Logger.Log(LogLevel.Info, $"Loading scene: {nextScene}");
                    SceneManager.LoadScene(nextScene);
                }
                else
                {
                    dumpingScenes = false;
                    Logger.Log(LogLevel.Info, $"Completed dumping all {totalScenesToDump} scenes");
                    MenuManager.GetMenu<MenuNotifications>().PushNotification($"Completed dumping all {totalScenesToDump} scenes", $"{Application.dataPath}{Path.DirectorySeparatorChar}*.dmp");                }
            }
        }

        public static void DumpAllScenes()
        {
            Scene activeScene = SceneManager.GetActiveScene();
            string[] sceneNames = SceneUtil.GetSceneNames();
            scenesToDump = new Queue<string>();

            for (int i = 0; i < sceneNames.Length; i++)
            {
                string sceneName = sceneNames[i];
                if (!sceneName.Equals("Start", StringComparison.OrdinalIgnoreCase) && !sceneName.Equals(activeScene.name, StringComparison.OrdinalIgnoreCase))
                    scenesToDump.Enqueue(sceneName);
            }
            scenesToDump.Enqueue("Start");

            totalScenesToDump = scenesToDump.Count;
            Logger.Log(LogLevel.Info, $"Started dumping all {totalScenesToDump} scenes");
            dumpingScenes = true;
        }

        public static List<FilteredFlowchart> GetFilteredFlowcharts(string filter)
        {
            if (HasPopulatedFlowcharts && (filteredFlowcharts == null || filteredText != filter))
            {
                filteredFlowcharts = new List<FilteredFlowchart>();
                filteredText = filter;
                FilterInputType filterInputType = GetFilterInputType(filteredText, out List<Type> typesFilter);

                for (int i = 0; i < flowcharts.Count; i++)
                {
                    Flowchart flowchart = flowcharts[i];
                    List<Variable> variables = new List<Variable>();
                    if (!string.IsNullOrEmpty(filteredText))
                    {
                        for (int j = 0; j < flowchart.Variables.Count; j++)
                        {
                            Variable variable = flowchart.Variables[j];
                            if(filterInputType == FilterInputType.Name && variable.Key.IndexOf(filteredText, StringComparison.OrdinalIgnoreCase) >= 0)
                                variables.Add(variable);
                            else if(filterInputType == FilterInputType.Types && typesFilter.Contains(variable.GetType()))
                                variables.Add(variable);
                        }
                    }
                    else
                    {
                        variables.AddRange(flowchart.Variables);
                    }

                    if(variables.Count > 0)
                    {
                        variables.Sort(variableComparer);
                        filteredFlowcharts.Add(new FilteredFlowchart(flowchart, variables));
                    }
                }
            }

            return filteredFlowcharts;
        }

        public static void AddInt(string name, int value)
        {
            if (!HasPopulatedFlowcharts) return;

            bool found = false;
            for (int j = 0; j < flowcharts.Count; j++)
            {
                if (found) break;

                for (int i = 0; i < flowcharts[j].Variables.Count; i++)
                {
                    if (found) break;

                    if (flowcharts[j].Variables[i].Key == name)
                    {
                        IntegerVariable variable = (IntegerVariable)flowcharts[j].Variables[i];
                        variable.Value += value;
                        PlayerPrefs.SetInt(SetSaveProfile.SaveProfile + "_" + flowcharts[j].SubstituteVariables(variable.Key), variable.Value);
                        found = true;
                    }
                }
            }
        }

        public static void SetInt(string name, int value)
        {
            if (!HasPopulatedFlowcharts) return;

            bool found = false;
            for (int j = 0; j < flowcharts.Count; j++)
            {
                if (found) break;

                for (int i = 0; i < flowcharts[j].Variables.Count; i++)
                {
                    if (found) break;

                    if (flowcharts[j].Variables[i].Key == name)
                    {
                        IntegerVariable variable = (IntegerVariable)flowcharts[j].Variables[i];
                        variable.Value = value;
                        PlayerPrefs.SetInt(SetSaveProfile.SaveProfile + "_" + flowcharts[j].SubstituteVariables(variable.Key), variable.Value);
                        found = true;
                    }
                }
            }
        }

        public static int GetInt(string name)
        {
            if (!HasPopulatedFlowcharts) return -1;

            int result = 0;
            bool found = false;
            for (int j = 0; j < flowcharts.Count; j++)
            {
                if (found) break;

                for (int i = 0; i < flowcharts[j].Variables.Count; i++)
                {
                    if (found) break;

                    if (flowcharts[j].Variables[i].Key == name)
                    {
                        IntegerVariable variable = (IntegerVariable)flowcharts[j].Variables[i];
                        result = variable.Value;
                        found = true;
                    }
                }
            }
            return result;
        }

        private static void InitFlowcharts()
        {
            InitCounter++;
            var fc = UnityEngine.Object.FindObjectsOfType<Flowchart>();
            if (fc != null && fc.Length != 0)
            {
                flowcharts = new List<Flowchart>(fc);
                FlowchartCount = flowcharts.Count;
                HasPopulatedFlowcharts = true;
            }
        }

        private static FilterInputType GetFilterInputType(string filter, out List<Type> types)
        {
            List<Type> resultTypes = new List<Type>();

            string[] filterParts = filter.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);

            for (int i = 0; i < filterParts.Length; i++)
            {
                string filterPart = filterParts[i].ToLower();
                switch (filterPart)
                {
                    case "type:int":
                        resultTypes.Add(typeof(IntegerVariable));
                        break;
                    case "type:float":
                        resultTypes.Add(typeof(FloatVariable));
                        break;
                    case "type:bool":
                        resultTypes.Add(typeof(BooleanVariable));
                        break;
                    case "type:str":
                        resultTypes.Add(typeof(StringVariable));
                        break;
                    default:
                        break;
                }
            }

            types = resultTypes;
            return types.Count == 0 ? FilterInputType.Name : FilterInputType.Types;
        }
    }
}
